package org.eclipse.epsilon.emc.spreadsheets.csv;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.epsilon.common.parse.AST;
import org.eclipse.epsilon.common.util.FileUtil;
import org.eclipse.epsilon.common.util.StringProperties;
import org.eclipse.epsilon.emc.spreadsheets.ISpreadsheetMetadata;
import org.eclipse.epsilon.emc.spreadsheets.ISpreadsheetMetadata.SpreadsheetWorksheetMetadata;
import org.eclipse.epsilon.emc.spreadsheets.MetadataXMLParser;
import org.eclipse.epsilon.emc.spreadsheets.SpreadsheetColumn;
import org.eclipse.epsilon.emc.spreadsheets.SpreadsheetModel;
import org.eclipse.epsilon.emc.spreadsheets.SpreadsheetRow;
import org.eclipse.epsilon.emc.spreadsheets.SpreadsheetUtils;
import org.eclipse.epsilon.emc.spreadsheets.SpreadsheetWorksheet;
import org.eclipse.epsilon.eol.EolModule;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.context.Variable;
import org.eclipse.epsilon.eol.models.IRelativePathResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class CsvModel extends SpreadsheetModel
{
	private static final Logger LOGGER = LoggerFactory.getLogger(CsvModel.class);

	public static final String PROPERTY_CSV_FILE = "PROPERTY_CSV_FILE";
	public static final String PROPERTY_CONFIG_FILE = "PROPERTY_CONFIG_FILE";
	public static final String PROPERTY_HAS_HEADER = "PROPERTY_HEADER_KNOWN";
	public static final String PROPERTY_SEPARATOR = "PROPERTY_SEPARATOR";

	private File file;
	private File configurationFile;
	private Document configurationDoc;
	private boolean hasHeader;
	private String separator;

	public CsvModel()
	{
		super();
		this.file = null;
		this.configurationFile = null;
		this.configurationDoc = null;
		this.hasHeader = CsvConstants.DEFAULT_HAS_HEADER;
		this.separator = CsvConstants.DEFAULT_SEPARATOR;
	}

	public static void main(final String[] args) throws Exception
	{
		CsvModel model = new CsvModel();
		if (args.length == 3)
		{
			model.setFile(args[0]);
			model.setConfigurationFile(args[1]);
			model.setName(args[2]);
		}
		model.setHasHeader(false);
		model.load();

		System.out.println("*** Executing EOL Code...");
		EolModule module = new EolModule();
		module.parse("CSV.all.println();");
		module.getContext().getModelRepository().addModel(model);
		module.execute();
	}

	public void setFile(final String pathToFile)
	{
		LOGGER.info("Path to CSV file: '" + pathToFile + "'");
		this.file = new File(pathToFile);
	}

	public void setConfigurationFile(final String configurationFilePath) throws ParserConfigurationException,
			SAXException, IOException
	{
		if (StringUtils.isNotBlank(configurationFilePath))
		{
			this.configurationFile = new File(configurationFilePath);
			final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
			this.configurationDoc = documentBuilder.parse(this.configurationFile);
		}
	}

	public boolean hasHeader()
	{
		return hasHeader;
	}

	public void setHasHeader(boolean hasHeader)
	{
		this.hasHeader = hasHeader;
	}

	public String getSeparator()
	{
		return separator;
	}

	public void setSeparator(String separator)
	{
		this.separator = separator;
	}

	@Override
	public void load(final StringProperties properties, final IRelativePathResolver resolver)
			throws EolModelLoadingException
	{
		super.load(properties, resolver);
		try
		{
			final String csvFilePath = properties.getProperty(CsvModel.PROPERTY_CSV_FILE);
			if (StringUtils.isNotBlank(csvFilePath))
			{
				this.setFile(resolver.resolve(csvFilePath));
			}
			final String configurationFilePath = properties.getProperty(CsvModel.PROPERTY_CONFIG_FILE);
			if (StringUtils.isNotBlank(configurationFilePath))
			{
				this.setConfigurationFile(resolver.resolve(configurationFilePath));
			}
			setSeparator(properties.getProperty(CsvModel.PROPERTY_SEPARATOR));
			setHasHeader(properties.getBooleanProperty(CsvModel.PROPERTY_HAS_HEADER, CsvConstants.DEFAULT_HAS_HEADER));
		}
		catch (Exception e)
		{
			throw new EolModelLoadingException(e, this);
		}
		this.load();
	}

	@Override
	protected void loadSpreadsheet() throws Exception
	{
		final CsvWorksheet csv = new CsvWorksheet(this, this.file);
		this.addWorksheet(csv);
	}

	@Override
	protected ISpreadsheetMetadata getSpreadsheetMetadata()
	{
		return new MetadataXMLParser(this.configurationDoc);
	}

	@Override
	protected boolean isMetadataConfigurationDefined()
	{
		return this.configurationFile != null && this.configurationDoc != null;
	}

	@Override
	protected SpreadsheetWorksheet createWorksheet(SpreadsheetWorksheetMetadata worksheetMetadata) throws Exception
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public Collection<SpreadsheetRow> find(Variable iterator, AST ast, IEolContext context) throws EolRuntimeException
	{
		throw new UnsupportedOperationException();
	}

	@Override
	protected void deleteWorksheet(SpreadsheetWorksheet worksheet)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean store()
	{
		return this.saveFile(this.file);
	}

	@Override
	public boolean store(final String location)
	{
		return this.saveFile(new File(location));
	}

	private boolean saveFile(final File file)
	{
		try
		{
			FileUtil.setFileContents(this.getContentsForFile(), file);
		}
		catch (Exception e)
		{
			LOGGER.error(e.getMessage());
			return false;
		}
		return true;
	}

	private String getContentsForFile()
	{
		final StringBuilder contentsForFile = new StringBuilder();
		final CsvWorksheet worksheet = (CsvWorksheet) this.getWorksheets().get(0);
		this.addHeader(contentsForFile, worksheet);
		this.addRows(contentsForFile, worksheet);
		return contentsForFile.toString();
	}

	private void addHeader(final StringBuilder sb, final CsvWorksheet worksheet)
	{
		if (this.hasHeader())
		{
			for (final SpreadsheetColumn column : worksheet.getHeader().getColumns())
			{
				sb.append(column.getIdentifier());
				sb.append(this.getSeparator());
			}
			SpreadsheetUtils.removeLast(sb, this.getSeparator());
			sb.append(System.getProperty("line.separator"));
		}
	}

	private void addRows(final StringBuilder sb, final CsvWorksheet worksheet)
	{
		final List<SpreadsheetRow> rows = worksheet.getRows();
		for (final SpreadsheetRow row : rows)
		{
			final CsvRow csvRow = (CsvRow) row;
			for (final String value : csvRow.getRow())
			{
				sb.append(value);
				sb.append(this.getSeparator());
			}
			SpreadsheetUtils.removeLast(sb, this.getSeparator());
			sb.append(System.getProperty("line.separator"));
		}
	}

}
